/*----------------------------------------------------------------------
 *
 * Copyright 2009, Thomas Dejanovic.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 *---------------------------------------------------------------------
 *
 * interface to simple apb slave bus.
 *
 *---------------------------------------------------------------------*/

module dcom_apb_if

  (/*AUTOARG*/
  // Outputs
  tx_data, tx_data_valid, pclk, pclk_en, gated_pclk, gated_pclk_en,
  preset_l, penable, psel, paddr, pwrite, pwdata,
  // Inputs
  clk, clk_en, reset, rx_data, rx_data_valid, rx_error, tx_ready,
  prdata
  );

  //----------------------------------------
  input                 clk;
  input                 clk_en;

  input                 reset;
  //----------------------------------------

  //----------------------------------------
  // data from the host
  input [7:0]           rx_data;
  input                 rx_data_valid;
  input                 rx_error;

  // data to be sent to the host.
  output [7:0]          tx_data;
  output                tx_data_valid;
  input                 tx_ready;
  //----------------------------------------

  //-----------------------------------------
  // apb interface.
  output                pclk;
  output                pclk_en;
  output                gated_pclk;
  output                gated_pclk_en;

  output                preset_l;
  output                penable;
  output                psel;
  output [29:0]         paddr;
  output                pwrite;
  output [31:0]         pwdata;
  input [31:0]          prdata;
  //-----------------------------------------



  /*----------------------------------------------------------------*/

  /*-AUTOUNUSED-*/

  /*AUTOINPUT*/

  /*AUTOOUTPUT*/

  /*-AUTOREGINPUT-*/

  /*AUTOREG*/

  /*AUTOWIRE*/

  /*------------------------------------------------------------------
   *
   * local definitions and connections.
   *
   * */

  assign                pclk = clk;
  assign                pclk_en = clk_en;
  assign                gated_pclk = clk;
  assign                gated_pclk_en = clk_en;

  assign                preset_l = ~reset;

  /*-----------------------------------------------------------------
   *
   * need a small Rx fifo.
   *
   * */

  reg    rx_fifo_pop;
  wire   rx_fifo_data_valid, rx_fifo_data_valid_int;
  wire [7:0] rx_fifo_data;

  reg [7:0]  rx_fifo_data_m [0:7];
  reg [2:0]  write_p, read_p;

  always @(posedge clk) if (clk_en) begin
    if (reset) begin
      rx_fifo_data_m[0] <= 8'd0;
      write_p <= 3'd0;
      read_p  <= 3'd0;
    end else begin
      if (rx_data_valid && (write_p != (read_p - 3'd1))) begin
        rx_fifo_data_m[write_p] <= rx_data;
        write_p <= write_p + 3'd1;
      end

      if (rx_fifo_pop && rx_fifo_data_valid_int) begin
        read_p <= read_p + 3'd1;
      end
    end
  end

  assign rx_fifo_data = rx_fifo_data_m[read_p];
  assign rx_fifo_data_valid_int = (read_p != write_p);
  assign rx_fifo_data_valid     = rx_fifo_data_valid_int & ~rx_fifo_pop;

  /*------------------------------------------------------------------
   *
   * def __init__(self, ser, OpCodes={'read':0x21,'write':0x22},
   * byteOrder='>', format='B', opCodeLen=2, addressLen=3, dataLen=2):
   *
   * 'format' is the format character used by the struct module for
   * packing/unpacking serial data (default 1 unsigned char from int.)
   *
   * 'opCodeLen' is the opcodes' length in bytes.
   *
   * 'addressLen' is the register address' length in bytes.
   *
   * 'dataLen' is the data bus width in bytes.  All data values
   * read/written to the serial port will be of this width.
   *
   *------------------------------------------------------------------
   *
   * main fsm.
   *
   * */

  parameter READ_OPCODE  = 8'h21;
  parameter WRITE_OPCODE = 8'h22;

  parameter [4:0] // synopsys enum state_info
               IDLE              = 4'd0,

               GET_ADDR          = 4'd1,
               GET_WRITE_DATA    = 4'd2,
               WRITE_COMMAND     = 4'd3,
               READ_COMMAND      = 4'd4,
               TX_READ_DATA      = 4'd5,

               ECHO_ERROR        = 4'd6,

               END               = 4'd15;

  reg [4:0] /* synopsys enum state_info */ state;

  /*AUTOASCIIENUM("state", "ascii_state", nul)*/
  // Beginning of automatic ASCII enum decoding
  reg [111:0]           ascii_state;            // Decode of state
  always @(state) begin
    case ({state})
      IDLE:           ascii_state = "idle          ";
      GET_ADDR:       ascii_state = "get_addr      ";
      GET_WRITE_DATA: ascii_state = "get_write_data";
      WRITE_COMMAND:  ascii_state = "write_command ";
      READ_COMMAND:   ascii_state = "read_command  ";
      TX_READ_DATA:   ascii_state = "tx_read_data  ";
      ECHO_ERROR:     ascii_state = "echo_error    ";
      END:            ascii_state = "end           ";
      default:        ascii_state = "%Error        ";
    endcase
  end
  // End of automatics

  //
  //
  // FSM
  //
  //

  reg [7:0]             tx_data;
  reg                   tx_data_valid;
  reg [3:0]             count;

  reg                   penable;
  reg                   psel;
  reg [29:0]            paddr;
  reg                   pwrite;
  reg [31:0]            pwdata;

  always @(posedge clk) if (clk_en) begin
    if (reset) begin
      state         <= IDLE;
      count         <= 4'd0;
      tx_data       <= 8'd0;
      tx_data_valid <= 1'd0;
      rx_fifo_pop   <= 1'd0;

      penable <= 1'd0;
      psel    <= 1'd0;
      paddr   <= 30'd0;
      pwrite  <= 1'd0;
      pwdata  <= 32'd0;
    end else begin
      tx_data_valid <= 1'd0;
      penable <= 1'd0;
      psel    <= 1'd0;
      rx_fifo_pop <= 1'd0;
      casez(state)
        IDLE: begin
          count   <= 4'd0;
          pwrite  <= 1'd0;
          if (rx_error) begin
            // do something sane here?
            state <= IDLE;
          end else if (rx_fifo_data_valid) begin
            pwrite  <= (rx_fifo_data == WRITE_OPCODE);
            count   <= 4'd0; // used to index address bytes.
            case (rx_fifo_data)
              READ_OPCODE  : begin
                state <= GET_ADDR;
                rx_fifo_pop <= 1'd1;
              end
              WRITE_OPCODE : begin
                state <= GET_ADDR;
                rx_fifo_pop <= 1'd1;
              end

              default      : begin
                state <= ECHO_ERROR;
                rx_fifo_pop <= 1'd0;
              end
            endcase
          end
        end

        GET_ADDR: begin
          if (rx_error) begin
            // do something sane here?
            state   <= IDLE;
            count   <= 4'd0;
            pwrite  <= 1'd0;
          end else if (rx_fifo_data_valid) begin
            rx_fifo_pop <= 1'd1;
            case(count)
              4'd0 : paddr[29:24] <= rx_fifo_data[5:0];
              4'd1 : paddr[23:16] <= rx_fifo_data;
              4'd2 : paddr[15: 8] <= rx_fifo_data;
              4'd3 : paddr[ 7: 0] <= rx_fifo_data;
            endcase // case(count)
            if (count == 4'd3) begin
              count   <= 4'd0;
              if (pwrite)
                state <= GET_WRITE_DATA;
              else
                state <= READ_COMMAND;
            end else begin
              count   <= count + 4'd1;
              state   <= GET_ADDR;
            end
          end else
            state   <= GET_ADDR;
        end

        GET_WRITE_DATA: begin
          if (rx_error) begin
            // do something sane here?
            state   <= IDLE;
            count   <= 4'd0;
            pwrite  <= 1'd0;
          end else if (rx_fifo_data_valid) begin
            rx_fifo_pop <= 1'd1;
            case(count)
              4'd0 : pwdata[31:24] <= rx_fifo_data;
              4'd1 : pwdata[23:16] <= rx_fifo_data;
              4'd2 : pwdata[15: 8] <= rx_fifo_data;
              4'd3 : pwdata[ 7: 0] <= rx_fifo_data;
            endcase // case(count)
            if (count == 4'd3) begin
              count <= 4'd0;
              state <= WRITE_COMMAND;
            end else begin
              count <= count + 4'd1;
              state <= GET_WRITE_DATA;
            end
          end else
            state <= GET_WRITE_DATA;
        end

        WRITE_COMMAND: begin
          /*              ___     ___     ___
             clk      ___/   \___/   \___/   \___
                                   _______
             penable  ____________/       \______
                           _______________
             psel     ____/               \______
                           _______________
             paddr    XXXXX_______________XXXXXXX
                           _______________
             pwdata   XXXXX_______________XXXXXXX
                                   _______
             prdata   XXXXXXXXXXXXX_______XXXXXXX
           */

          case(count)
            4'd0 : begin
              penable <= 1'd0;
              psel    <= 1'd1;
            end
            4'd1 : begin
              penable <= 1'd1;
              psel    <= 1'd1;
            end
            default : begin
              penable <= 1'd0;
              psel    <= 1'd0;
            end
          endcase // case(count)
          if (count == 4'd2) begin
            count <= 4'd0;
            state <= IDLE;
          end else begin
            count <= count + 4'd1;
            state <= WRITE_COMMAND;
          end
        end

        READ_COMMAND: begin
          case(count)
            4'd0 : begin
              penable <= 1'd0;
              psel    <= 1'd1;
            end
            4'd1 : begin
              penable <= 1'd1;
              psel    <= 1'd1;
            end
            default : begin
              penable <= 1'd0;
              psel    <= 1'd0;
            end
          endcase // case(count)
          if (count == 4'd2) begin
            count  <= 4'd0;
            pwdata <= prdata;
            state  <= TX_READ_DATA;
          end else begin
            count <= count + 4'd1;
            state <= READ_COMMAND;
          end
        end

        TX_READ_DATA: begin
          if (tx_ready & ~tx_data_valid) begin
            case(count)
              4'd0 : begin
                tx_data       <= pwdata[31:24];
                tx_data_valid <= 1'd1;
                count         <= count + 4'd1;
              end
              4'd1 : begin
                tx_data       <= pwdata[23:16];
                tx_data_valid <= 1'd1;
                count         <= count + 4'd1;
              end
              4'd2 : begin
                tx_data       <= pwdata[15: 8];
                tx_data_valid <= 1'd1;
                count         <= count + 4'd1;
              end
              4'd3 : begin
                tx_data       <= pwdata[ 7: 0];
                tx_data_valid <= 1'd1;
                count         <= count + 4'd1;
              end
              default : begin
                tx_data       <= 8'd0;
                tx_data_valid <= 1'd0;
              end
            endcase // case(count)
          end else if (tx_ready & tx_data_valid) begin
            // wait for ready to be deasserted.
            tx_data_valid <= 1'd1;
          end else begin
            // busy processing request.
            if (count == 4'd4) begin
              count  <= 4'd0;
              state  <= IDLE;
            end else begin
              state <= TX_READ_DATA;
            end
          end
        end // case: TX_READ_DATA

        ECHO_ERROR : begin
          if (tx_ready & ~tx_data_valid) begin
            case(count)
              4'd0 : begin
                tx_data       <= rx_fifo_data;
                tx_data_valid <= 1'd1;
                count         <= count + 4'd1;
                rx_fifo_pop   <= 1'd1;
              end
              default : begin
                tx_data       <= 8'd0;
                tx_data_valid <= 1'd0;
                rx_fifo_pop   <= 1'd0;
              end
            endcase // case(count)
          end else if (tx_ready & tx_data_valid) begin
            // wait for ready to be deasserted.
            tx_data_valid <= 1'd1;
          end else begin
            // busy processing request.
            if (count == 4'd1) begin
              count  <= 4'd0;
              state  <= IDLE;
            end else begin
              state <= ECHO_ERROR;
            end
          end
        end // case: ECHO_ERROR

        default : begin
          state <= IDLE;
        end
      endcase // casez(state)
    end
  end

  /*----------------------------------------------------------------*/

endmodule // dcom_apb_if


// Local Variables:
// verilog-library-directories:(".")
// verilog-library-extensions:(".v")
// End:
